///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
*	Contains code for planes.
*	\file		LaborPlane.h
*	\author		Pierre Terdiman
*	\date		April, 4, 2000
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Include Guard
#ifndef __LABORPLANE_H__
#define __LABORPLANE_H__
#include "../LaborCoreDef.h"
#include "../Math/LaborMathDecl.h"
#include "LaborMatrix4Function.h"
#include "LaborMatrix3Function.h"
//#	ifdef _DEBUG
//
//#		pragma message ("-------------------LaborPlane.h")
//
//#	endif
NAMESPACE_LABOR3D_BEGIN
#define PLANE_EPSILON		(1.0e-7f)

class  LABORCORE_API CLPlane
{
public:
	//! Constructor
	inline			CLPlane()															{												}
	//! Constructor from a normal and a distance
	inline			CLPlane(float nx, float ny, float nz, float d)						{ set(nx, ny, nz, d);							}
	//! Constructor from a point on the CLPlane and a normal
	inline			CLPlane(const CLPoint3& p, const CLPoint3& n)							{ set(p, n);									}
	//! Constructor from three points
	inline			CLPlane(const CLPoint3& p0, const CLPoint3& p1, const CLPoint3& p2)		{ set(p0, p1, p2);								}
	//! Constructor from a normal and a distance
	inline			CLPlane(const CLPoint3& _n, float _d)								{ n = _n; d = _d;								}
	//! Copy constructor
	inline			CLPlane(const CLPlane& plane) : n(plane.n), d(plane.d)				{												}
	//! Destructor
	inline			~CLPlane()														{												}

	inline	CLPlane&	zero()															{ n.zero(); d = 0.0f;			return *this;	}
	inline	CLPlane&	set(float nx, float ny, float nz, float _d)						{ n.set(nx, ny, nz); d = _d;	return *this;	}
	inline	CLPlane&	set(const CLPoint3& p, const CLPoint3& _n)							{ n = _n; d = - p | _n;			return *this;	}
	CLPlane&	set(const CLPoint3& p0, const CLPoint3& p1, const CLPoint3& p2);

	inline	float	distance(const CLPoint3& p)			const						{ return (p | n) + d;							}
	inline	bool	belongs(const CLPoint3& p)			const						{ return fabsf(distance(p)) < PLANE_EPSILON;	}

	enum Side
	{
		NO_SIDE,
		POSITIVE_SIDE,
		NEGATIVE_SIDE
	};

	inline	Side getSide (const CLPoint3& p) const
	{
		float fDistance = distance(p);

		if ( fDistance < 0.0 )
			return CLPlane::NEGATIVE_SIDE;

		if ( fDistance > 0.0 )
			return CLPlane::POSITIVE_SIDE;

		return CLPlane::NO_SIDE;
	}

	//! Returns an intersection with a 3d line.
	//! \param lineVect: Vector of the line to intersect with.
	//! \param linePoint: Point of the line to intersect with.
	//! \param outIntersection: Place to store the intersection point, if there is one.
	//! \return Returns true if there was an intersection, false if there was not.
	bool getIntersectionWithLine(const CLPoint3& linePoint, const CLPoint3& lineVect, CLPoint3& outIntersection) const
	{
		float t2 = n.dot(lineVect);

		if (t2 == 0)
			return false;

		float t =- (n.dot(linePoint) + d) / t2;			
		outIntersection = linePoint + (lineVect * t);
		return true;
	}

	//! Intersects this plane with another.
	//! \return Returns true if there is a intersection, false if not.
	inline	bool getIntersectionWithPlane(const CLPlane& other, CLPoint3& outLinePoint, CLPoint3& outLineVect) const
	{
		float fn00 = n.length();
		float fn01 = n.dot(other.n);
		float fn11 = other.n.length();
		float det = fn00*fn11 - fn01*fn01;

		if (fabs(det) < 1e-08f)
			return false;

		det = 1.0f / det;
		float fc0 = (fn11*-d + fn01*other.d) * det;
		float fc1 = (fn00*-other.d + fn01*d) * det;

		outLineVect = n.cross(other.n);
		outLinePoint = n*(float)fc0 + other.n*(float)fc1;
		return true;
	}

	//! Returns the intersection point with two other planes if there is one.
	inline	bool getIntersectionWithPlanes(const CLPlane& o1, const CLPlane& o2, CLPoint3& outPoint) const
	{
		CLPoint3 linePoint, lineVect;
		if (getIntersectionWithPlane(o1, linePoint, lineVect))
			return o2.getIntersectionWithLine(linePoint, lineVect, outPoint);

		return false;
	}

	inline	void	normalize()
	{
		float Denom = 1.0f / n.magnitude();
		n.x	*= Denom;
		n.y	*= Denom;
		n.z	*= Denom;
		d	*= Denom;
	}
public:
	// Members
	CLPoint3	n;		//!< The normal to the plane
	float	d;		//!< The distance from the origin

	// Cast operators
	inline			operator CLPoint3()					const						{ return n;										}
	inline			operator CLPoint4()					const						{ return CLPoint4(n, d);							}

	// Arithmetic operators
	inline	CLPlane	operator*(const CLMatrix4x4& m)		const
	{
		// Old code from Irion. Kept for reference.
		CLPlane Ret(*this);
		return Ret *= m;
	}

	inline	CLPlane&	operator*=(const CLMatrix4x4& m)
	{
		// Old code from Irion. Kept for reference.
		CLPoint4 ptIn = CLPoint4(n, 0.0f);
		LM_Mul( m, ptIn  );
		CLPoint3 n2 = CLPoint3( ptIn);

		ptIn = CLPoint4(-d * n, 0.0f);

		LM_Mul( m, ptIn );
		CLPoint3 n3 = CLPoint3( ptIn);;


		d = -(n3 | n2);
		n = n2;
		return *this;
	}
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
*	Transforms a plane by a 4x4 matrix. Same as CLPlane * Matrix4x4 operator, but faster.
*	\param		transformed	[out] transformed plane
*	\param		plane		[in] source plane
*	\param		transform	[in] transform matrix
*	\warning	the plane normal must be unit-length
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
inline	void transformPlane(CLPlane& transformed, const CLPlane& plane, const CLMatrix4x4& transform)
{
	// Rotate the normal using the rotation part of the 4x4 matrix
	//transformed.n = plane.n * CLMatrix3x3(transform);
	LM_Mul( CLMatrix3x3(transform), plane.n, transformed.n);
	// Compute new d
	CLPoint3 ptTrans3;
	transform.getTrans( ptTrans3 );
	transformed.d = plane.d - (ptTrans3|transformed.n);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
*	Transforms a plane by a 4x4 matrix. Same as CLPlane * Matrix4x4 operator, but faster.
*	\param		plane		[in/out] source plane (transformed on return)
*	\param		transform	[in] transform matrix
*	\warning	the plane normal must be unit-length
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
inline	void transformPlane(CLPlane& plane, const CLMatrix4x4& transform)
{
	// Rotate the normal using the rotation part of the 4x4 matrix
	//plane.n *= CLMatrix3x3(transform);
	LM_Mul( CLMatrix3x3(transform), plane.n, plane.n);

	// Compute new d
	CLPoint3 ptTrans;
	transform.getTrans( ptTrans );
	plane.d -= ptTrans | plane.n;
}

NAMESPACE_LABOR3D_END

#endif // __LABORPLANE_H__
